package love.lemoncat.websocket.msg;

import com.alibaba.fastjson.JSONObject;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lovelemoncat
 */
@Slf4j
@Data
public abstract class AbstractMsg<T extends AbstractMsg, C extends Serializable> implements Msg<T> {
    private String devNo;
    private DataType dataType;
    private C data;
    private Map attr = new HashMap();

    private Class<T> getArgsType() {
        // 目的：得到实际类型参数
        // 得到当前运行对象
        Class<?> clazz = this.getClass();
        // 得到当前对象父类的参数化类型,一般使用type子接口ParameterizedType
        Type type = clazz.getGenericSuperclass();
        ParameterizedType ptype = (ParameterizedType) type;
        // 得到实际类型参数
        Type[] types = ptype.getActualTypeArguments();
        return (Class<T>) types[0];
    }

    public AbstractMsg() {
    }


    @Override
    public String encode() throws IllegalAccessException, IntrospectionException, InvocationTargetException {
        HashMap<Object, Object> map = new HashMap<>();
        map.put(Msg.DEV_NO, this.devNo);
        map.put(Msg.CONTENT, this.data);
        if (this.attr != null && this.attr.size() > 0) {
            map.putAll(this.attr);
        } else {
            map.put(Msg.ATTR, null);
        }

        Map<String, Object> objMap = AbstractMsg.objectToMap(this);
        map.putAll(objMap);
        return JSONObject.toJSONString(this);
    }

    @Override
    public T decode(String msg) throws InvocationTargetException, IntrospectionException, InstantiationException, IllegalAccessException {
        Class<T> argsType = this.getArgsType();
        if (StringUtils.hasText(msg)) {
            HashMap<String, Object> map = JSONObject.parseObject(msg, HashMap.class);
            return AbstractMsg.mapToObject(map, argsType);
        } else {
            throw new RuntimeException("消息不能为空");
        }
    }

    @Override
    public TextWebSocketFrame encodeTextFrame() throws IllegalAccessException, IntrospectionException, InvocationTargetException {
        String encode = this.encode();
        return new TextWebSocketFrame(encode);
    }

    @Override
    public T decodeTextFrame(TextWebSocketFrame msg) throws Exception {
        String text = msg.text();
        return this.decode(text);
    }

    public static <T> T mapToObject(Map<String, Object> map, Class<T> beanClass) throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
        if (map == null) {
            return null;
        }
        T obj = null;
        obj = beanClass.newInstance();
        BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor property : propertyDescriptors) {
            Method setter = property.getWriteMethod();
            if (setter == null) {
                continue;
            }
            String name = property.getName();
            Object value = map.get(property.getName());
            if (name.equals(Msg.DATA_TYPE)) {
                if (map.containsKey(name)) {
                    value = DataType.valueOf((String) map.get(name));
                }
            }
            if (value != null) {
                setter.invoke(obj, value);
            }
        }
        return obj;
    }

    public static <T> Map<String, Object> objectToMap(T obj) throws InvocationTargetException, IllegalAccessException, IntrospectionException {
        if (obj == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        BeanInfo beanInfo = null;
        beanInfo = Introspector.getBeanInfo(obj.getClass());
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor property : propertyDescriptors) {
            String key = property.getName();
            if (key.compareToIgnoreCase("class") == 0) {
                continue;
            }
            Method getter = property.getReadMethod();
            Object value = getter != null ? getter.invoke(obj) : null;
            map.put(key, value);
        }
        return map;
    }

    @Override
    public String toString() {
        try {
            return this.encode();
        } catch (Exception e) {
            log.error("{}", e);
            throw new RuntimeException("消息解析失败");
        }
    }
}
